#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

#include "socket_wrap.h"

#define SERV_PORT 6667

int main(int argc, char *argv[])
{
    int i, j, n;
    int nready;//返回查询readset中有事件的socket个数
    int maxfd = 0;
    int listenfd, connfd;
    char buf[BUFSIZ];      
    struct sockaddr_in clie_addr, serv_addr;
    socklen_t clie_addr_len;
    int client[FD_SETSIZE];//文件描述符最大为1024
    char str[INET_ADDRSTRLEN];//ipv4字符串需要16字节存储

    //s
    listenfd = Socket(AF_INET, SOCK_STREAM, 0);  

    //端口复用(有问题)
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    //b
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family= AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port= htons(SERV_PORT);
    Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    //l
    Listen(listenfd, 128);
    

    //select
    //假设有以下socket自己带入代码试一下，就能理解为什么分别有readset、allset了
    // lfd:2
    // readset:4 5 66 
    // allset   :2 4 5 66 77
    fd_set readset;   //备份：记录所有已连接的socket(包括特殊的lfd)
    fd_set allset;    //记录哪些socket有请求（包括特殊的lfd）

    maxfd = listenfd;
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset); /* 构造select监控文件描述符集 */
    while (1) {   
        readset = allset;  /* 每次循环时都从新设置select监控信号集 */
        nready = select(maxfd+1, &readset, NULL, NULL, NULL);//参数5 NULL阻塞等待 
        if (nready < 0)
            perr_exit("select error");
            
        //先判断lfd有没有事件请求,有则只处理lfd
        if (FD_ISSET(listenfd, &readset)) {
            //Accept不会阻塞:因为能到这一步说明客户端已在请求连接
            clie_addr_len = sizeof(clie_addr);
            connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);

            //lfd解放，返回新的一个连接也添加到监视中
            FD_SET(connfd, &allset);

            //始终记录集合中socket描述符的最大值
            if (maxfd < connfd)
                maxfd = connfd;

            //nready为1时触发（满足的事件总数为1）:即只有lfd有事情，其他socket没啥事
            if (1==nready)
                continue;
        } 

        //这里效率明显有瑕疵，for遍历太浪费时间。用c++ 的set容器或哈希表存储socket文件描述符    
        //循环看看哪些socket有事情(除开lfd)
        for (i = listenfd+1; i <= maxfd; ++i) {//listenfd+1是因为lfd门卫它不会有read()请求
            if (FD_ISSET(i, &readset)) {

                if ((n = Read(i, buf, sizeof(buf))) == 0) {//客户端close()发来EOF结束
                    Close(i);
                    FD_CLR(i, &allset);                  //解除select对此文件描述符的监控
                } else if (n > 0) {//写回客户端
                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    Write(i, buf, n);
                }
            }
        }
    }

    Close(listenfd);
    return 0;
}

